%{
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

struct wordstat {
	char *s;
	signed long freq;
};

struct cont {
	struct wordstat *w;
	size_t len;
	size_t cap;
};

typedef enum { false = 0, true = 1 } bool_t;

static struct cont kwds, names;
static long INSIDE_ML_COMMENT = 0;

static void add_word(struct cont *, const char *);
static void sort_container(struct cont *);
static void print_results(struct cont *);
static void free_container(struct cont *);

static int cmp_words(const void *, const void *);
/*static char *strdup(const char *);*/
%}

NOTWORD ([ \t\n\v\r]|"."|,|-|:|;|"|"|"["|"]"|[(){}<>]|[*&])
C_KEYWORDS (int|long|signed|unsigned|char|const|static|volatile|register|void|extern|asm|return|if|for|while|do|else|goto|float|switch|case|typedef|struct|enum)
CPP_KEYWORDS (include|define|undef|ifdef|ifndef|endif)

%%

"\r" { REJECT; } /* no carriage-returns in 2013, please */

{NOTWORD} { ECHO; }

"//".*$ { fprintf(stderr, "skip single-line comment: <%s>\n", yytext); }

^#[ \t]*{CPP_KEYWORDS}.*$ { fprintf(stderr, "skip preprocessor directive: <%s>\n", yytext); } /* eat preprocessor directives */

"/*" { INSIDE_ML_COMMENT++; }

"*/" { INSIDE_ML_COMMENT = 0; }

{C_KEYWORDS} {
  ECHO;
 }

[a-zA-Z_][a-zA-Z0-9_-]* {
  ECHO;
 }

. { ECHO; }

%%

int yywrap(void) { return 1; }

int main(int argc, char **argv) {
	yylex();

	sort_container(&kwds);
	sort_container(&names);

	fputs("keywords:\n", stdout);
	print_results(&kwds);
	fputs("names:\n", stdout);
	print_results(&names);

	free_container(&kwds);
	free_container(&names);

	return 0;
}

void add_word(struct cont *c, const char *w) {
	if (c == NULL || w == NULL) {
		fprintf(stderr, "add_word(): bad arguments: c=%p, w=%p\n", c, w);
		return;
	}

	if (c->cap == 0UL || c->w == NULL) {
		c->w = calloc(100UL, sizeof c->w[0]);
		c->cap = 100UL;
		c->len = 0UL;
	}

	if (c->len == 0UL) {
		c->w[0].s = strdup(yytext);
		c->w[0].freq = 1L;
		c->len++;
		return;
	}

	if (c->len == c->cap - 1UL) {
		struct wordstat *tmp;
		tmp = realloc(c->w, (c->cap + 100UL) * sizeof c->w[0]);
		if (tmp == NULL) {
			fprintf(stderr, "add_word(): out of memory (tried to reallocate %lu bytes)\n",
					c->cap + 100UL);
			return;
		}
		c->cap += 100UL;
		c->w = tmp;
	}

	register int i;
	bool_t found = false;
	for (i = 0; i < c->len; i++) {
		if (!strcmp(w, c->w[i].s)) {
			c->w[i].freq++;
			found = true;
			break;
		}
	}
	if (!found) {
		assert(i == c->len);
		c->len++;
		c->w[i].s = strdup(yytext);
		c->w[i].freq = 1L;
	}
}

void sort_container(struct cont *c) {
	qsort(c->w, c->len, sizeof c->w[0], cmp_words);
}

void print_results(struct cont *c) {
	register int i;
	for (i = 0; i < c->len; i++) {
		printf("%ld\t%s\n", c->w[i].freq, c->w[i].s);
	}
}

void free_container(struct cont *c) {
	register int i;
	for (i = 0; i < c->len; i++) {
		free(c->w[i].s);
	}
}

int cmp_words(const void *p1, const void *p2) {
	struct wordstat *s1, *s2;

	if (p1 == NULL || p2 == NULL) {
		fprintf(stderr, "cmp_words(): bad arguments: p1=%p, p2=%p\n",
				p1, p2);
		return 0;
	}

	s1 = p1;
	s2 = p2;

	if (s1->freq < s2->freq)
		return 1;
	else if (s1->freq > s2->freq)
		return -1;

	return 0;
}

/*
char *strdup(const char *s) {
	size_t len;
	char *ptr;

	if (s == NULL) {
		fprintf(stderr, "strdup(): bad arguments: s=%p\n", s);
		return NULL;
	}

	len = strlen(s);
	ptr = malloc(len);
	strcpy(ptr, s);

	return ptr;
}
*/
